package com.cocostudio.erpapp;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Handler;
import android.os.Message;
import androidx.loader.content.AsyncTaskLoader;
import android.util.Log;
import android.widget.Toast;

import com.cocostudio.erpapp.common.Debug;
import com.cocostudio.erpapp.db.DBHelper;
import com.cocostudio.erpapp.vo.ValueObject;
import com.cocostudio.erpapp.web.ServerRequester;
import com.cocostudio.erpapp.web.ServerRequester.Parameters;
import com.cocostudio.erpapp.web.ServerRequester.Result;

public class CustomCursorLoader extends AsyncTaskLoader<Cursor> {
    private static final String TAG = "CustomLoader";

    /**
     * Load data from db at first, if have none, loading from web and cache it
     * in db.
     */
    public static final int FLAG_DEFAULT = 0;
    /**
     * Sync data from web, and then cache it
     */
    public static final int FLAG_FORCE_SYNC = 1;
    /**
     * Get data from db forcedly.
     */
    public static final int FLAG_FORCE_LOCAL = 2;

    private String mAPI;
    private Parameters mParameters;
    private OnPersistenceListener mDBListener;
    private int mLoadFlag = FLAG_FORCE_SYNC;
    private Cursor mData;
    private OnLoadInBackgroundCallback mCallback;
    private Handler errorHandler;

    public CustomCursorLoader(Context context, String api, Parameters params, OnPersistenceListener listener) {
        this(context, api, params, listener, null);
    }

    public CustomCursorLoader(Context context, String api, Parameters params, OnPersistenceListener listener, OnLoadInBackgroundCallback callback) {
        super(context);
        mAPI = api;
        mParameters = params;
        mDBListener = listener;
        mCallback = callback;
        errorHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Toast.makeText(getContext(), R.string.msg_access_network_failed, Toast.LENGTH_SHORT).show();
            }
        };
    }

    @Override
    public Cursor loadInBackground() {
        if (Debug.LOGD) {
            Log.d(TAG, "loadInBackground.");
        }

        Cursor c = null;
        if (FLAG_DEFAULT == mLoadFlag) {
            // load data from db at first
            c = loadFromDB();
            if (!isDataValid(c)) {
                c = syncWithServer();
            }
        } else if (FLAG_FORCE_SYNC == mLoadFlag) {
            c = syncWithServer();
            mLoadFlag = FLAG_DEFAULT;
        } else if (FLAG_FORCE_LOCAL == mLoadFlag) {
            c = loadFromDB();
            mLoadFlag = FLAG_DEFAULT;
        }
        if (mCallback != null) {
            int savedPos = c.getPosition();
            mCallback.onLoadInBackground(mAPI, mParameters, c);
            c.moveToPosition(savedPos);
        }
        return c;
    }

    private boolean isDataValid(Cursor c) {
        return mData != null && mData.moveToFirst();
    }

    private Cursor syncWithServer() {
        if (Debug.LOGD) {
            Log.d(TAG, "syncWithServer. ");
        }
        SQLiteDatabase db = DBHelper.openDatabase(getContext(), true);
        Result result = ServerRequester.requestByGet(mAPI, mParameters);
        if (result.isOk()) {
            // save to db
            mDBListener.onSaveToDB(db, mAPI, mParameters, result.vo);
        } else {
            errorHandler.sendEmptyMessage(0);
        }
        return mDBListener.onLoadFromDB(db, mAPI, mParameters);
    }

    public Cursor loadFromDB() {
        if (Debug.LOGD) {
            Log.d(TAG, "loadFromDB. ");
        }
        SQLiteDatabase db = DBHelper.openDatabase(getContext(), true);
        return mDBListener.onLoadFromDB(db, mAPI, mParameters);
    }

    /**
     * Called when there is new data to deliver to the client. The super class
     * will take care of delivering it; the implementation here just adds a
     * little more logic.
     */
    @Override
    public void deliverResult(Cursor c) {
        if (Debug.LOGD) {
            Log.d(TAG, "deliverResult. isReset=" + isReset());
        }

        if (isReset()) {
            // An async query came in while the loader is stopped. We
            // don't need the result.
            if (c != null) {
                onReleaseResources(c);
            }
        }
        Cursor old = mData;
        mData = c;

        if (isStarted()) {
            // If the Loader is currently started, we can immediately
            // deliver its results.
            super.deliverResult(c);
        }

        // At this point we can release the resources associated with
        // 'oldApps' if needed; now that the new result is delivered we
        // know that it is no longer in use.
        if (old != null && old != mData) {
            onReleaseResources(old);
        }
    }

    /**
     * Handles a request to start the Loader.
     */
    @Override
    protected void onStartLoading() {
        if (Debug.LOGD) {
            Log.d(TAG, "onStartLoading. mLoadFlag=" + mLoadFlag);
        }
        if (mLoadFlag != FLAG_DEFAULT || mData == null || mData.isClosed()) {
            forceLoad();
        } else {
            deliverResult(mData);
        }
    }

    /**
     * Handles a request to stop the Loader.
     */
    @Override
    protected void onStopLoading() {
        if (Debug.LOGD) {
            Log.d(TAG, "onStopLoading. ");
        }
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    /**
     * Handles a request to cancel a load.
     */
    @Override
    public void onCanceled(Cursor c) {
        if (Debug.LOGD) {
            Log.d(TAG, "onCanceled. ");
        }
        super.onCanceled(c);

        // At this point we can release the resources associated with 'apps'
        // if needed.
        onReleaseResources(c);
    }

    /**
     * Handles a request to completely reset the Loader.
     */
    @Override
    protected void onReset() {
        if (Debug.LOGD) {
            Log.d(TAG, "onReset. ");
        }
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        // At this point we can release the resources associated with 'apps'
        // if needed.
        if (mData != null) {
            onReleaseResources(mData);
            mData = null;
        }
    }

    /**
     * Helper function to take care of releasing resources associated with an
     * actively loaded data set.
     */
    protected void onReleaseResources(Cursor c) {
        if (Debug.LOGD) {
            Log.d(TAG, "onReleaseResources. ");
        }
        // For a simple List<> there is nothing to do. For something
        // like a Cursor, we would close it here.
        if (c != null && !c.isClosed()) {
            c.close();
            c = null;
        }
    }

    /**
     * Reload data with default flag.
     * 
     * @see {@link CustomCursorLoader#FLAG_DEFAULT},
     *      {@link CustomCursorLoader#FLAG_FORCE_SYNC},
     *      {@link CustomCursorLoader#FLAG_FORCE_LOCAL}
     */
    public void reload(String api, Parameters params) {
        mAPI = api;
        mParameters = params;
        mLoadFlag = FLAG_DEFAULT;
        startLoading();
    }

    /**
     * Reload data with flag.
     * 
     * @see {@link CustomCursorLoader#FLAG_DEFAULT},
     *      {@link CustomCursorLoader#FLAG_FORCE_SYNC},
     *      {@link CustomCursorLoader#FLAG_FORCE_LOCAL}
     */
    public void reload(String api, Parameters params, int loadFlag) {
        mAPI = api;
        mParameters = params;
        mLoadFlag = loadFlag;
        startLoading();
    }

    /**
     * Reload data with flag.
     * 
     * @see {@link CustomCursorLoader#FLAG_DEFAULT},
     *      {@link CustomCursorLoader#FLAG_FORCE_SYNC}
     */
    public void reload(int loadFlag) {
        mLoadFlag = loadFlag;
        startLoading();
    }

    public interface OnPersistenceListener {
        public void onSaveToDB(SQLiteDatabase db, String api, Parameters params, ValueObject object);

        public Cursor onLoadFromDB(SQLiteDatabase db, String api, Parameters params);
    }

    public interface OnLoadInBackgroundCallback {
        public void onLoadInBackground(String api, Parameters params, Cursor c);
    }
}
